Skip to content

Store response objects in a Vec with ArcStr names - #1416

Merged
LegNeato merged 1 commit into
graphql-rust:masterfrom
LegNeato:response-objects
Nov 9, 2026
Merged

LegNeato merged 1 commit into
graphql-rust:masterfrom
LegNeato:response-objects

Conversation

@LegNeato

@LegNeato LegNeato commented Nov 1, 2026

Copy link
Copy Markdown
Member

This PR builds on #1415. Only its last commit is new.

  • Object stores its fields in a Vec in insertion order instead of an IndexMap with String keys. A lookup compares the names one by one. For the small field counts of GraphQL objects, this is faster than hashing.
  • Above 16 fields, Object builds a foldhash index of the names. A selection set with thousands of aliases thus cannot make execution quadratic. A Box keeps the index out of Value, so Value stays at 32 bytes.
  • The field names are ArcStr. A field without an alias reuses the ArcStr name from the schema, so its response name needs no allocation. The asynchronous executor used to allocate each name twice.
  • Object merges fields with the same response name. This logic moved from the executor into Object.

On the benchmarks of #1413, synchronous execution takes 4 to 44% less time than with #1415, and asynchronous execution takes 4 to 39% less time. A synchronous execution of 1000 objects with 6 fields each makes 4061 allocations instead of 11,063.

Each resolved field allocated a `String` for its response name. The
asynchronous executor allocated the name twice. `Object` also hashed
each name with SipHash up to three times per field. It hashed the name
to find an existing field and to insert the new one.

`Object` stores its fields in a `Vec` in insertion order. A lookup
compares the names one by one. For the small field counts of GraphQL
objects, this is faster than hashing. Above 16 fields, `Object` builds
a hash index of the names, so a selection set with thousands of aliases
cannot make execution quadratic. A `Box` keeps the index out of
`Value`, so `Value` stays at 32 bytes. The field names are `ArcStr`. A
field without an alias reuses the `ArcStr` name from the schema, so its
response name needs no allocation. The logic that merges fields with
the same response name moved from the executor into `Object`.

A synchronous execution of 1000 objects with 6 fields each now makes
4061 allocations instead of 11,063. On the benchmark operations,
synchronous execution takes 4 to 44% less time, and asynchronous
execution takes 4 to 39% less time.

New tests cover adding, merging, and comparing object fields, objects
with an index, and the insertion order.
@LegNeato
LegNeato merged commit 7680526 into graphql-rust:master Nov 9, 2026
184 checks passed
@LegNeato
LegNeato deleted the response-objects branch November 9, 2026 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant